﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.Types;
using IWMAG.Scripting.Interpreter;

namespace IWMAG.Scripting.AST.Operators {
    internal abstract class BinaryOperator : CodeExpression {
        public CodeExpression LHS;
        public CodeExpression RHS;

        public BinaryOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l) {
            LHS = left;
            RHS = right;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            LHS.Verify(sym, error);
            RHS.Verify(sym, error);
        }
    }

    internal abstract class TypedBinaryOperator : BinaryOperator {
        protected CodeType Type;

        public TypedBinaryOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right, CodeType t) 
            : base(l, left, right) {
            Type = t;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (LHS.TypeOf() != Type || RHS.TypeOf() != Type) {
                SendError(error, "Arguments to typed binary operator must be of type " + Type.ToString());
            }
        }
    }

    internal abstract class SameTypedBinaryOperator : TypedBinaryOperator {
        public SameTypedBinaryOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right, CodeType t) 
            : base(l, left, right, t) { }

        public override CodeType TypeOf() {
            return Type;
        }
    }

    internal abstract class IntRealBinaryOperator : BinaryOperator {
        public IntRealBinaryOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override CodeType TypeOf() {
            return (LHS.TypeOf() == CodeType.Real || RHS.TypeOf() == CodeType.Real)
                ? CodeType.Real : CodeType.Int;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (!((LHS.TypeOf() == CodeType.Real || LHS.TypeOf() == CodeType.Int) 
                && (RHS.TypeOf() == CodeType.Real || RHS.TypeOf() == CodeType.Int))) {
                SendError(error, "Unable to perform math between " + LHS.TypeOf().ToString() + " and " + RHS.TypeOf().ToString());
            }
        }
    }

    internal class AddOperator : BinaryOperator {
        public AddOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override CodeType TypeOf() {
            if (LHS.TypeOf() == CodeType.String) {
                return CodeType.String;
            }
            return (LHS.TypeOf() == CodeType.Real || RHS.TypeOf() == CodeType.Real)
                ? CodeType.Real : CodeType.Int;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (LHS.TypeOf() != CodeType.String && !((LHS.TypeOf() == CodeType.Real || LHS.TypeOf() == CodeType.Int)
                && (RHS.TypeOf() == CodeType.Real || RHS.TypeOf() == CodeType.Int))) {
                SendError(error, "Unable to perform math between " + LHS.TypeOf().ToString() + " and " + RHS.TypeOf().ToString());
            }
        }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is string || right.ReferencedValue.Value is string) {
                val.ReferencedValue.Value = left.ReferencedValue.Value.ToString() + right.ReferencedValue.Value.ToString();
            } else if (left.ReferencedValue.Value is double || right.ReferencedValue.Value is double) {
                val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                    + (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
            } else {
                val.ReferencedValue.Value = (int) Marshaller.Cast(left.ReferencedValue.Value, typeof(int), engine.CompiledTable)
                    + (int) Marshaller.Cast(right.ReferencedValue.Value, typeof(int), engine.CompiledTable);
            }
        }
    }

    internal class SubOperator : IntRealBinaryOperator {
        public SubOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is double || right.ReferencedValue.Value is double) {
                val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                    - (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
            } else {
                val.ReferencedValue.Value = (int) Marshaller.Cast(left.ReferencedValue.Value, typeof(int), engine.CompiledTable)
                    - (int) Marshaller.Cast(right.ReferencedValue.Value, typeof(int), engine.CompiledTable);
            }
        }
    }

    internal class MulOperator : IntRealBinaryOperator {
        public MulOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is double || right.ReferencedValue.Value is double) {
                val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                    * (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
            } else {
                val.ReferencedValue.Value = (int) Marshaller.Cast(left.ReferencedValue.Value, typeof(int), engine.CompiledTable)
                    * (int) Marshaller.Cast(right.ReferencedValue.Value, typeof(int), engine.CompiledTable);
            }
        }
    }

    internal class DivOperator : IntRealBinaryOperator {
        public DivOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                / (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
        }
    }

    internal class GtOperator : IntRealBinaryOperator {
        public GtOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }
        public override CodeType TypeOf() { return CodeType.Bool; }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is double || right.ReferencedValue.Value is double) {
                val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                    > (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
            } else {
                val.ReferencedValue.Value = (int) Marshaller.Cast(left.ReferencedValue.Value, typeof(int), engine.CompiledTable)
                    > (int) Marshaller.Cast(right.ReferencedValue.Value, typeof(int), engine.CompiledTable);
            }
        }
    }

    internal class LtOperator : IntRealBinaryOperator {
        public LtOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }
        public override CodeType TypeOf() { return CodeType.Bool; }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is int) {
                val.ReferencedValue.Value = left.ReferencedValue.IntValue < right.ReferencedValue.IntValue;
            } else {
                val.ReferencedValue.Value = left.ReferencedValue.RealValue < right.ReferencedValue.RealValue;
            }
        }
    }

    internal class GteqOperator : IntRealBinaryOperator {
        public GteqOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }
        public override CodeType TypeOf() { return CodeType.Bool; }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is double || right.ReferencedValue.Value is double) {
                val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                    >= (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
            } else {
                val.ReferencedValue.Value = (int) Marshaller.Cast(left.ReferencedValue.Value, typeof(int), engine.CompiledTable)
                    >= (int) Marshaller.Cast(right.ReferencedValue.Value, typeof(int), engine.CompiledTable);
            }
        }
    }

    internal class LteqOperator : IntRealBinaryOperator {
        public LteqOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }
        public override CodeType TypeOf() { return CodeType.Bool; }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            if (left.ReferencedValue.Value is double || right.ReferencedValue.Value is double) {
                val.ReferencedValue.Value = (double) Marshaller.Cast(left.ReferencedValue.Value, typeof(double), engine.CompiledTable)
                    <= (double) Marshaller.Cast(right.ReferencedValue.Value, typeof(double), engine.CompiledTable);
            } else {
                val.ReferencedValue.Value = (int) Marshaller.Cast(left.ReferencedValue.Value, typeof(int), engine.CompiledTable)
                    <= (int) Marshaller.Cast(right.ReferencedValue.Value, typeof(int), engine.CompiledTable);
            }
        }
    }

    internal class ModOperator : SameTypedBinaryOperator {
        public ModOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.IntValue % right.ReferencedValue.IntValue;
        }
    }

    internal class LShiftOperator : SameTypedBinaryOperator {
        public LShiftOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.IntValue << (int)right.ReferencedValue.IntValue;
        }
    }

    internal class RShiftOperator : SameTypedBinaryOperator {
        public RShiftOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.IntValue >> (int)right.ReferencedValue.IntValue;
        }
    }

    internal class BitAndOperator : SameTypedBinaryOperator {
        public BitAndOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.IntValue & right.ReferencedValue.IntValue;
        }
    }

    internal class BitOrOperator : SameTypedBinaryOperator {
        public BitOrOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.IntValue | right.ReferencedValue.IntValue;
        }
    }

    internal class BitXorOperator : SameTypedBinaryOperator {
        public BitXorOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Int) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.IntValue ^ right.ReferencedValue.IntValue;
        }
    }

    internal class BoolAndOperator : SameTypedBinaryOperator {
        public BoolAndOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Bool) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;

            if (!left.ReferencedValue.BoolValue) {
                val.ReferencedValue.Value = false;
                yield break;
            }

            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.BoolValue && right.ReferencedValue.BoolValue;
        }
    }

    internal class BoolOrOperator : SameTypedBinaryOperator {
        public BoolOrOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right, CodeType.Bool) { }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;

            if (left.ReferencedValue.BoolValue) {
                val.ReferencedValue.Value = true;
                yield break;
            }

            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.BoolValue || right.ReferencedValue.BoolValue;
        }
    }

    internal class EqOperator : BinaryOperator {
        public EqOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (LHS.TypeOf() != RHS.TypeOf()) {
                SendError(error, "Can't compare " + LHS.TypeOf().ToString() + " with " + RHS.TypeOf().ToString() + ".");
            }
        }

        public override CodeType TypeOf() {
            return CodeType.Bool;
        }

        public override IEnumerable<Continuation> ValueOf(Interpreter.ScriptEngine engine, Helpers.RuntimeTable sym, Interpreter.Reference<Interpreter.RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = left.ReferencedValue.Value.Equals(right.ReferencedValue.Value);
        }
    }

    internal class NeqOperator : BinaryOperator {
        public NeqOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (LHS.TypeOf() != RHS.TypeOf()) {
                SendError(error, "Can't compare " + LHS.TypeOf().ToString() + " with " + RHS.TypeOf().ToString() + ".");
            }
        }

        public override IEnumerable<Continuation> ValueOf(Interpreter.ScriptEngine engine, Helpers.RuntimeTable sym, Interpreter.Reference<Interpreter.RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue.Value = !left.ReferencedValue.Value.Equals(right.ReferencedValue.Value);
        }

        public override CodeType TypeOf() {
            return CodeType.Bool;
        }
    }

    internal class IndexOperator : BinaryOperator {
        protected CodeType Type;

        public IndexOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (!(LHS.TypeOf() is ArrayType)) {
                SendError(error, "Attempt to index a non-array.");
            }

            if (!RHS.TypeOf().Equals(CodeType.Int)) {
                SendError(error, "Index must be of type int.");
            }

            Type = (LHS.TypeOf() as ArrayType).BaseType;
        }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            val.ReferencedValue = left.ReferencedValue.ArrayValue[(int)right.ReferencedValue.IntValue];
        }

        public override CodeType TypeOf() {
            return Type;
        }
    }

    internal class RangeOperator : BinaryOperator {
        public RangeOperator(Compiler.Lexeme l, CodeExpression left, CodeExpression right) : base(l, left, right) { }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            base.Verify(sym, error);

            if (!(LHS.TypeOf() == RHS.TypeOf() && LHS.TypeOf() == CodeType.Int)) {
                SendError(error, "Range operator expects both operands to be of type int.");
            }

        }

        public override IEnumerable<Continuation> ValueOf(ScriptEngine engine, Helpers.RuntimeTable sym, Reference<RunValue> val) {
            val.ReferencedValue.Value = new ArrayRunValue();

            Reference<RunValue> left = RunValue.NoValue;
            Reference<RunValue> right = RunValue.NoValue;
            foreach (Continuation wait in LHS.ValueOf(engine, sym, left)) yield return wait;
            foreach (Continuation wait in RHS.ValueOf(engine, sym, right)) yield return wait;

            for (int i = 0; i <= right.ReferencedValue.IntValue - left.ReferencedValue.IntValue; i++) {
                val.ReferencedValue.ArrayValue.Add(new RunValue(i + left.ReferencedValue.IntValue));
            }
        }

        public override CodeType TypeOf() {
            return new ArrayType(CodeType.Int);
        }
    }
}
